/**
 * $version:  0.1 
 * $Date: 2012-05-14 
 *
 * Copyright (C) 2010-2011 Jawa Software. All rights reserved.
 *
 */

package org.jawa.core.database;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.jawa.core.util.JawaGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 检查系统程序当前数据库的版本是否需要升级.
 */
public class SchemaManager {

	private static final Logger Log = LoggerFactory.getLogger(SchemaManager.class);

	private static final String CHECK_VERSION = "SELECT prop_value FROM app_prop WHERE name='jawa.version' ";

	/**
	 * 当前数据库版本.
	 */
	private static final int DATABASE_VERSION = 1;

	/**
	 * SchemaManager 实例化.
	 */
	SchemaManager() {
	}
	
	/**
	 * 检查当前数据库是否需要升级,如果数据库不存在将尝试创建.
	 *
	 * @param con 数据库连接.
	 * @return true 升级或安装成功.
	 */
	public boolean checkSchema(Connection con) {
		try {
			return checkSchema(con, "jawa", DATABASE_VERSION, new ResourceLoader() {
				@Override
				public InputStream loadResource(String resourceName) {
					File file = new File(JawaGlobals.getHomeDirectory() + File.separator + "resources" + File.separator
							+ "database", resourceName);
					try {
						return new FileInputStream(file);
					} catch (FileNotFoundException e) {
						return null;
					}
				}
			});
		} catch (Exception e) {
			Log.error("数据库更新失败!");
		}
		return false;
	}

	/**
	 * 检查数据库是否需要升级,在启动程序的时候进行调用.
	 *
	 * @param con 数据库连接.
	 * @param schemaKey 数据库.
	 * @param requiredVersion 数据库的版本.
	 * @param resourceLoader 数据库脚本加载.
	 * @throws Exception 如果发生错误.
	 * @return 数据库是否更新或创建成功.
	 */
	private boolean checkSchema(Connection con, String schemaKey, int requiredVersion, ResourceLoader resourceLoader)
			throws Exception {
		int currentVersion = -1;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = con.prepareStatement(CHECK_VERSION);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				currentVersion = rs.getInt(1);
			}
		} catch (SQLException sqle) {
			Log.debug("SchemaManager: 检测数据库版本发生错误! " + schemaKey, sqle);
		} finally {
			DbConnectionManager.closeStatement(rs, pstmt);
		}
		// 已经更新.
		if (currentVersion >= requiredVersion) {
			return true;
		}
		// 数据库没有安装,进行安装
		else if (currentVersion == -1) {
			Log.info("数据库没有安装,进行安装", schemaKey);
			// Resource will be like "/database/jawa_hsqldb.sql"
			String resourceName = schemaKey + "_" + DbConnectionManager.getDatabaseType() + ".sql";
			InputStream resource = resourceLoader.loadResource(resourceName);
			if (resource == null) {
				return false;
			}
			try {
				executeSQLScript(con, resource);
			} catch (Exception e) {
				Log.error(e.getMessage(), e);
				return false;
			} finally {
				try {
					resource.close();
				} catch (Exception e) {
					// Ignore.
				}
			}
			Log.info("创建数据库成功:" + schemaKey);
			return true;
		}
		// 数据库需要升级
		else {
			Log.info("数据库版本需要升级");

			// 未知类型的数据库类型,无法进行升级.
			if (DbConnectionManager.getDatabaseType() == DbConnectionManager.DatabaseType.unknown) {
				System.out.println("未知的数据库类型");
				return false;
			}

			//运行到当前版本的所有更新.
			for (int i = currentVersion + 1; i <= requiredVersion; i++) {
				InputStream resource = getUpgradeResource(resourceLoader, i, schemaKey);
				if (resource == null) {
					continue;
				}
				try {
					executeSQLScript(con, resource);
				} catch (Exception e) {
					Log.error(e.getMessage(), e);
					return false;
				} finally {
					try {
						resource.close();
					} catch (Exception e) {
						// 忽略.
					}
				}
			}
			Log.info("升级数据库版本成功!");
			return true;
		}
	}

	private InputStream getUpgradeResource(ResourceLoader resourceLoader, int upgradeVersion, String schemaKey) {
		InputStream resource = null;
		String path = JawaGlobals.getHomeDirectory() + File.separator + "resources" + File.separator + "database"
				+ File.separator + "upgrade" + File.separator + upgradeVersion;
		String filename = schemaKey + "_" + DbConnectionManager.getDatabaseType() + ".sql";
		File file = new File(path, filename);
		try {
			resource = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			// 发生错误,升级文件不存在.
		}
		return resource;
	}

	/**
	 * 执行SQL脚本.
	 *
	 * @param con 数据库连接.
	 * @param resource 资源文件.
	 * @throws IOException 如果出现一个输入\输出错误.
	 * @throws SQLException 如果出现一个数据库错误.
	 */
	private static void executeSQLScript(Connection con, InputStream resource) throws IOException, SQLException {
		BufferedReader in = null;
		try {
			in = new BufferedReader(new InputStreamReader(resource));
			boolean done = false;
			while (!done) {
				StringBuilder command = new StringBuilder();
				while (true) {
					String line = in.readLine();
					if (line == null) {
						done = true;
						break;
					}
					// 忽略注释与空行.
					if (isSQLCommandPart(line)) {
						command.append(" ").append(line);
					}
					if (line.trim().endsWith(";")) {
						break;
					}
				}
				if (!done && !command.toString().equals("")) {
					// 如果使用Oracle或DB2,删除最后一个分号;
					if (DbConnectionManager.getDatabaseType() == DbConnectionManager.DatabaseType.oracle
							|| DbConnectionManager.getDatabaseType() == DbConnectionManager.DatabaseType.db2) {
						command.deleteCharAt(command.length() - 1);
					}
					PreparedStatement pstmt = null;
					try {
						String cmdString = command.toString();
						pstmt = con.prepareStatement(cmdString);
						pstmt.execute();
					} catch (SQLException e) {
						Log.error("SchemaManager: Failed to execute SQL:\n" + command.toString());
						throw e;
					} finally {
						DbConnectionManager.closeStatement(pstmt);
					}
				}
			}
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					Log.error(e.getMessage(), e);
				}
			}
		}
	}

	private static abstract class ResourceLoader {

		public abstract InputStream loadResource(String resourceName);

	}

	/**
	 * 检测当前数据行是否为一个可执行的命令行.
	 *
	 * @param line 数据库命令行.
	 * @return true 是否为一个可执行的命令行.
	 */
	private static boolean isSQLCommandPart(String line) {
		line = line.trim();
		if (line.equals("")) {
			return false;
		}
		// 检测是否是一个注释行:
		// "REM" : Oracle
		// "//" : HSQLDB
		// "--" : DB2 和 Postgres
		// "#" : MySQL
		// "/*" : SQLServer
		return !(line.startsWith("//") || line.startsWith("--") || line.startsWith("#") || line.startsWith("REM")
				|| line.startsWith("/*") || line.startsWith("*"));
	}
}